/*******************************************************************************
 * Copyright (c) 2005, 2014 springx.github.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 *******************************************************************************/
package com.springx.shiro;
import com.springx.modules.utils.Encodes;
import com.springx.shiro.domain.Account;
import com.springx.shiro.domain.Principal;
import com.springx.shiro.domain.ShiroRole;
import com.springx.shiro.service.ShiroAccountService;
import com.springx.starter.utils.SpringContextHolder;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import java.util.List;

public class ShiroDbRealm extends AuthorizingRealm {
    private static Logger logger = LoggerFactory.getLogger(ShiroDbRealm.class);
    protected ShiroAccountService shiroAccountService;

    /**
     * 认证回调函数,登录时调用.
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        Account account = shiroAccountService.findAccountByLoginName(token.getUsername());
        if (account != null) {
            if (!account.getIsEnabled()) {
                throw new DisabledAccountException();
            }

            byte[] salt = Encodes.decodeHex(account.getSalt());
            return new SimpleAuthenticationInfo(
                    new Principal(account.getUsername()),
                    account.getPassword(),
                    ByteSource.Util.bytes(salt),
                    getName()
            );
        } else {
            return null;
        }
    }

    /**
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Principal principal = (Principal) principals.getPrimaryPrincipal();
        List<ShiroRole> roleList = shiroAccountService.findRoleListByLoginName(principal.getUsername());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        boolean isSystem = false;
        for (ShiroRole role : roleList) {
            //基于Role的权限信息
            if (StringUtils.isNotBlank(role.getCode())) {
                info.addRole(role.getCode());
            }
            // 基于Permission的权限信息
            if (null == role.getIsSystem() ? false : role.getIsSystem()) {//如果是系统内置角色,将拥有所有权限
                info.addStringPermissions(shiroAccountService.findAllPermissionList());//所有资源权限
                break;
            } else {
                info.addStringPermissions(shiroAccountService.findPermissionListByRole(role.getId()));
            }
        }

        return info;
    }

    /**
     * 设定Password校验的Hash算法与迭代次数.
     */
    @PostConstruct
    public void initCredentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(ShiroAccountService.HASH_ALGORITHM);
        matcher.setHashIterations(ShiroAccountService.HASH_INTERATIONS);
        setCredentialsMatcher(matcher);
    }


    /**
     * 通过登录名移除认证信息
     *
     * @return boolean
     */

    public boolean clearCachedAuthenticationInfo() {
        try {
            clearCachedAuthenticationInfo(SecurityUtils.getSubject().getPrincipals());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 清除所有用户授权信息缓存.
     */
    public void clearAllCachedAuthorizationInfo() {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if (cache != null) {
            for (Object key : cache.keys()) {
                cache.remove(key);
            }
        }
    }


    /**
     * 解决在登录时无法加载菜单项的问题,即在每次登录时重新加载用户权限缓存
     */
    public static void forceShiroToReloadUserAuthorityCache(String username) {
        ShiroDbRealm shiroDbRealm = SpringContextHolder.getBean(ShiroDbRealm.class);
        shiroDbRealm.clearCachedAuthenticationInfo(); // 清除权限缓存
        shiroDbRealm.isPermitted(SecurityUtils.getSubject().getPrincipals(),
                "强制shiro检查加载用户权限缓存,避免懒加载!" + System.currentTimeMillis());
    }

    public void setShiroAccountService(ShiroAccountService shiroAccountService) {
        this.shiroAccountService = shiroAccountService;
    }
}
